<?php
namespace system\libraries;

use system\core\Config;
use system\libraries\database\ActiveRecord;

/**
 * Session Library
 *
 * @author Steffen Brem <steffenbrem@gmail.com>
 * @package system.libraries
 */
class Session 
{
    private $_sessionId;
    private $_userAgent;
    private $_ipAddress;
    private $_lastActivity;
    
    private $_cookieName;
    private $_cookiePath;
    
    private $_useDatabase = FALSE;
    private $_tableName;
    
    private $_expirationTime = 3600;
    
    private $_data   = array();
    private $_flash  = array();
    
    /**
     * ActiveRecord
     * 
     * @var system\libraries\database\ActiveRecord
     */
    private $_ar;
    
    /**
     * Stores a Session instance
     * 
     * @var system\libaries\Session
     */
    private static $_instance;
    
    /**
     * Constructor
     * 
     * @throws \Exception 
     */
    private function __construct()
    {
        $this->_cookieName  = Config::get('sessions', 'cookie_name');
        $this->_cookiePath  = Config::get('sessions', 'cookie_path');
        
        $this->_useDatabase = Config::get('sessions', 'use_database');
        $this->_tableName   = Config::get('sessions', 'table_name');
        
        // Get a database connection if needed
        if ( $this->_useDatabase == TRUE )
        {
            $this->_ar = new ActiveRecord();   
        }
        
        if ( ! session_id() )
        {
            if ( ini_set('session.name', $this->_cookieName) === FALSE
                    || ! session_name($this->_cookieName) )
            {
                throw new \Exception('Could not set session name! Maybe it contains invalid characters?');
            }
            
            session_start();
        }
        
            
        $this->_sessionId = session_id();

        // Read the current session
        if ( ! $this->_read() )
        {
            // The session data is not valid, lets setup a valid one!
            $this->_setup();
        }
        else
        {
            // It seems that the there is already valid session data, update
            // this data...
            $this->_update();
        }
        
        $this->_sessionId = session_id();
        
    }
    
    /**
     * Get the Session instance
     * 
     * @return system\libraries\Session
     */
    public static function getInstance()
    {
        if ( ! self::$_instance instanceof Session )
        {
            self::$_instance = new Session();
        }
        
        return self::$_instance;
    }
    
    /**
     * Set session data
     * 
     * You can either pass an array with the data, or set the session data
     * one by one by giving the key and value as arguments.
     * 
     * @param string/array $key
     * @param string $value 
     */
    public function setData( $key, $value = NULL )
    {
        if ( is_array($key) )
        {
            foreach ( $key as $key => $data )
            {
                $this->_data[$key] = $data;
            }
        }
        else if ( $value !== NULL )
        {
            $this->_data[$key] = $value;
        }
        else
        {
            throw new \Exception('Could not set session data. You must set a value or pass an array.');
        }
        
        $this->_write();
    }
    
    /**
     * Fetch session data
     * 
     * @param string $index
     * @return string 
     */
    public function data( $key )
    {
        return ( isset($this->_data[$key]) ) ? $this->_data[$key] : FALSE;
    }
    
    /**
     * Set flash data
     * 
     * @param string $index
     * @param string $value 
     */
    public function setFlash( $key, $value )
    {
        $this->_flash[$key] = array(
            'data' => $value,
            'flag' => 'new'
        );
        
        $this->_write();
    }
    
    /**
     * Keeps the flash data for 1 more request
     * 
     * @param string $key
     */
    public function keepFlash( $key )
    {
        $this->_flash[$key]['flag'] = 'keep';
        
        $this->_write();
    }
    
    /**
     * Fetch flash data
     * 
     * @param string $index
     * @return string/bool false if not is set 
     */
    public function flash( $key )
    {
        return ( isset($this->_flash[$key]['data']) ) ? $this->_flash[$key]['data'] : FALSE;
    }
    
    /**
     * Clear ALL the session data
     */
    public function destroy()
    {        
        if ( $this->_useDatabase == TRUE )
        {
            $this->_ar->where('session_id', $this->_sessionId);
            $this->_ar->delete($this->_tableName);
        }
        
        unset($_SESSION);
        session_destroy();
        session_start();
    }
    
    /**
     * Read the session
     */
    private function _read()
    {
        $session = $this->_getSessionData();

        if ( ! isset($session['session_id']) 
                || ! isset($session['user_agent']) 
                || ! isset($session['ip_address'])
                || ! isset($session['last_activity']) )
        {
            $this->destroy();
            return FALSE;
        }
        
        if ( $session['user_agent'] != $_SERVER['HTTP_USER_AGENT'] )
        {
            $this->destroy();
            return FALSE;
        }
        
        if ( $session['ip_address'] != $_SERVER['REMOTE_ADDR'] )
        {
            $this->destroy();
            return FALSE;
        }
        
        if ( $session['last_activity'] + $this->_expirationTime < time() )
        {
            $this->destroy();
            return FALSE;
        }
        
        // Everything is OK, lets set the properties
        $this->_userAgent    = $_SERVER['HTTP_USER_AGENT'];
        $this->_ipAddress    = $_SERVER['REMOTE_ADDR'];
        $this->_lastActivity = time();
        
        $this->_data  = @unserialize($session['data']);
        $this->_flash = @unserialize($session['flash']);
        
        return TRUE;
    }
    
    /**
     * Get the raw session data
     * 
     * @return array
     */
    private function _getSessionData()
    {
        if ( $this->_useDatabase == TRUE )
        {
            $this->_ar->where('session_id', $this->_sessionId);
            return $this->_ar->get($this->_tableName)->row();
        }
        
        return $_SESSION;
    }
    
    /**
     * Write the data to the session (TO DO: or database)
     */
    private function _write()
    {
        if ( $this->_useDatabase == TRUE )
        {
            $this->_writeToDatabase();
        }
        else
        {
            $this->_writeToSession();
        }
    }
    
    /**
     * Write all the data to the session
     */
    private function _writeToSession()
    {
        $_SESSION['session_id']    = $this->_sessionId;
        $_SESSION['user_agent']    = $this->_userAgent;
        $_SESSION['ip_address']    = $this->_ipAddress;
        $_SESSION['last_activity'] = $this->_lastActivity; 

        $_SESSION['data']  = serialize($this->_data);
        $_SESSION['flash'] = serialize($this->_flash);
    }
    
    /**
     * Write all the data to the database
     */
    private function _writeToDatabase()
    {
        $this->_ar->where('session_id', $this->_sessionId);
        $rowCount = $this->_ar->rowCount($this->_tableName);

        $insert = array(
            'session_id' => $this->_sessionId,
            'user_agent' => $this->_userAgent,
            'ip_address' => $this->_ipAddress,
            'last_activity' => $this->_lastActivity,
            'data' => serialize($this->_data),
            'flash' => serialize($this->_flash)
        );

        $update = array(
            'user_agent' => $this->_userAgent,
            'ip_address' => $this->_ipAddress,
            'last_activity' => $this->_lastActivity,
            'data' => serialize($this->_data),
            'flash' => serialize($this->_flash)
        );

        ( $rowCount > 0 ) ? $this->_ar->update($this->_tableName, $update) : $this->_ar->insert($this->_tableName, $insert);
    }
    
    /**
     * Setup the session, so we can use it properly
     */
    private function _setup()
    {
        $this->_sessionId    = session_id();
        $this->_userAgent    = $_SERVER['HTTP_USER_AGENT'];
        $this->_ipAddress    = $_SERVER['REMOTE_ADDR'];
        $this->_lastActivity = time();
        
        $this->_write();
    }
    
    /**
     * Update the session
     */
    private function _update()
    {
        $this->_unsetFlash();
        
        // We also update the last activity (last time session was updated)
        $this->_lastActivity = time();
        
        $this->_write();
    }
    
    /**
     * Unset flash data if it is old 
     */
    private function _unsetFlash()
    {
        // If there is no flash data, we have nothing to do...
        if ( ! $this->_flash || count($this->_flash) == 0 ) { return; }

        foreach ( $this->_flash as $key => $flashData )
        {
            if ( $flashData['flag'] == 'keep' )
            {
                $this->_flash[$key]['flag'] = 'new';
            }
            else if ( $flashData['flag'] == 'new' )
            {
                $this->_flash[$key]['flag'] = 'old';
            }
            else
            {
                unset($this->_flash[$key]);
            }
        }
    }
}